-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: the main entry of sensor application.
local class = require 'mc.class'
local log = require 'mc.logging'
local signal = require 'mc.signal'
local mc_utils = require 'mc.utils'
local utils_core = require 'utils.core'
local file_sec = require 'utils.file'
local json = require 'cjson'
local base_service = require 'sensor.service'
local client = require 'sensor.client'
local database = require 'sensor_database'
local sensor_customize = require 'sensor_customize'
local sensor_management = require 'sensor.sensor_management'
local sdr_management = require 'sdr.sdr_management'
local sel_management = require 'sel.sel_management'
local pef_management = require 'pef.pef_management'
local entity_management = require 'entity.entity_management'
local global_management = require 'global.global_management'
local mdb_manager = require 'mc.mdb.object_manage'
local reboot = require 'mc.mdb.micro_component.reboot'
local debug = require 'mc.mdb.micro_component.debug'
local config = require 'mc.mdb.micro_component.config_manage'
local utils = require 'sensor_utils'
local mc_admin = require 'mc.mc_admin'
local base_messages = require 'messages.base'
local custom_messages = require 'messages.custom'
local skynet = require 'skynet'
require 'sensor_mdb_object'

local SENSORS_MDB_INTF<const> = "bmc.kepler.Chassis.Sensors"

local sensor_service = class(base_service)

function sensor_service:exit()
    self.db.db:close()
    self.local_db.db:close()
end

-- 循环GC避免内存占用过高
local function start_gc_task()
    skynet.fork_loop({count = 0}, function()
        skynet.sleep(60000) -- 10分钟后开始循环GC
        log:notice('start collect garbage')
        while true do
            collectgarbage('collect')
            skynet.sleep(6000) -- 每60sGC一次
        end
    end)
end

function sensor_service:init_mgr()
    self.systems = {}
    self.database = database.new(self.db, self.local_db, self.reset_local_db)
    self.sel_sigs = {
        add = signal.new(), remove = signal.new(), find = signal.new(),
        post = signal.new(), get = signal.new(), rearm = signal.new(),
        enable = signal.new()
    }
    self.entity_sigs = {update = signal.new(), getName = signal.new(), getStatus = signal.new()}
    self.sensor_sigs = {update = signal.new(), addsel = signal.new()}
    self.global_sigs = {update = signal.new()}
    self.sdr_sigs = {update = signal.new()}
    self.sensor_mgr = sensor_management.new(self.database,
        self.sensor_sigs, self.sel_sigs, self.entity_sigs, self.sdr_sigs, self.global_sigs)
    self.sdr_mgr = sdr_management.new(self.sdr_sigs)
    self.sel_mgr = sel_management.new(self.bus, self.database, self.sel_sigs, self.sensor_sigs)
    self.pef_mgr = pef_management.new(self.database)
    self.entity_mgr = entity_management.new(self.entity_sigs, self.sensor_sigs)
    self.global_mgr = global_management.new(self.sel_sigs, self.global_sigs)
    self.sensor_mgr:initialize()
    self.sdr_mgr:initialize()
    self.sel_mgr:initialize()
    self.pef_mgr:initialize()
    self.entity_mgr:initialize()
    self.global_mgr:initialize()
end

function sensor_service:init_ipmi()
    self.sensor_mgr:register_ipmi(function (...) self:register_ipmi_cmd(...) end)
    self.sdr_mgr:register_ipmi(function (...) self:register_ipmi_cmd(...) end)
    self.sel_mgr:register_ipmi(function (...) self:register_ipmi_cmd(...) end)
    self.pef_mgr:register_ipmi(function (...) self:register_ipmi_cmd(...) end)
    self.global_mgr:register_ipmi(function (...) self:register_ipmi_cmd(...) end)
end

function sensor_service:init_objmgr()
    mdb_manager.on_add_object(self.bus, function(...) self:register(...) end)
    mdb_manager.on_add_object_complete(self.bus, function (position)
        self.sensor_mgr:register_discrete_events(position)
    end)
    mdb_manager.on_delete_object(self.bus, function(...) self:unregister(...) end)
end

-- 打包路径：/dev/shm/sensor
local PACKED_PATH = '/dev/shm/sensor'
function sensor_service:dump_sdr(mode)
    log:info('dump sdr start ...')
    local ok, reservation_id, count, records = pcall(function()
        return self.sdr_mgr:get_sdr_list()
    end)
    if not ok then
        log:error('dump sdr records failed: %s', reservation_id)
        return
    end

    log:info('dump sdr records completed ...')
    -- 创建打包文件的路径和文件
    if file_sec.check_real_path_s(PACKED_PATH) ~= 0 and utils_core.mkdir(PACKED_PATH, mc_utils.S_IRWXU) ~= 0 then
        log:error('temporary is not existed and cannot be created, dump sdr failed')
        return
    end
    local file = PACKED_PATH .. (mode == 'LAN' and '/ipmi_sdr.lan' or '/ipmi_sdr.bt')
    local ok, f = pcall(io.open, file, 'wb')
    if not ok or not f then
        log:error('dump sdr failed, err: %s', f)
        return
    end
    f:write(skynet.packstring(reservation_id, count, records))
    f:close()
    log:info('dump sdr list and packed completed ...')
end

function sensor_service:init_rpc()
    -- 配置默认的管理host对应的RPC接口，防止默认host的传感器没有register或者无传感器时导致无法调用接口
    local default_chassis_id = 1
    if not self.systems[default_chassis_id] then
        self.systems[default_chassis_id] = 'default'
        self.sensors_class = self:CreateSensors(default_chassis_id, function(...) return end)
        self.sensors_obj = self.sensors_class:get_mdb_object(SENSORS_MDB_INTF)
        self.global_mgr:set_sensors_obj(self.sensors_obj)
        log:info('register default chassis %d RPC resources.', default_chassis_id)
    end

    self:ImplSensorsSensorsGetThresholdSensorList(function(obj, ctx, ...)
        return self.sensor_mgr:get_threshold_sensor_list(...)
    end)
    self:ImplSensorsSensorsMockSensor(function(obj, ctx, ...)
        return self.sensor_mgr:mock_sensor(obj, ctx:get_initiator(), ...)
    end)
    self:ImplSensorsSensorsGetSensorList(function(obj, ctx, ...)
        return self.sensor_mgr:get_all_sensor_list(...)
    end)
    self:ImplSensorsSensorsSetSensorStatus(function (obj, ctx, ...)
        return self.sensor_mgr:set_sensor_status(obj, ctx:get_initiator(), ...)
    end)
    self:ImplSensorsSensorsExportSel(function(obj, ctx, ...)
        return self.sel_mgr:export_sel(ctx, self.bus, ...)
    end)
    self:ImplSensorsSensorsGetSel(function(obj, ctx, ...)
        return self.sel_mgr:get_sel(...)
    end)
    self:ImplSensorsSensorsGetDiscreteSensorList(function (obj, ctx, ...)
        return self.sensor_mgr:get_discrete_sensor_list(...)
    end)
    self:ImplSensorsSensorsGetReportedSel(function(obj, ctx, ...)
        return self.sel_mgr:report_sel(...)
    end)
    self:ImplSensorsSensorsDumpSDR(function (obj, ctx, mode)
        return self:dump_sdr(mode)
    end)
    self:ImplSensorsSensorsAddOemSel(function (obj, ctx, ...)
        return self.sel_mgr:add_oem_sel(...)
    end)
    self:ImplSensorsSensorsClearSel(function (obj, ctx)
        return self.sel_mgr:rpc_clear_sel(ctx)
    end)
end

function sensor_service:on_reboot_prepare()
    if not self.sel_mgr:check_reboot_permitted() then
        log:notice('wait sel updating overtime(5 seconds)')
    end

    return 0
end

function sensor_service:on_reboot_action()
    log:info('sensor has no extra action for reboot.')
    return 0
end

function sensor_service:on_reboot_cancel()
    self.sel_mgr:clear_reset_flag()
    log:notice('clear the reset flag after reboot is cancelled')
end

function sensor_service:init_reboot()
    reboot.on_prepare(function(...) return self:on_reboot_prepare(...) end)
    reboot.on_action(function(...) return self:on_reboot_action(...) end)
    reboot.on_cancel(function(...) return self:on_reboot_cancel(...) end)
end

function sensor_service:on_dump(ctx, path)
    local file = require 'utils.file'
    if file.check_realpath_before_open_s(path) == -1 then
        log:error('[on_dump] path is invalid.')
        return
    end

    -- 1. 收集 sensor list
    self.sensor_mgr:dump_sensor_list(path .. '/sensor_info.txt')

    -- 2. 收集 sdr list
    self.sdr_mgr:dump_sdr_list(path .. '/sdr_info.txt')

    -- 3. 收集 sel 数据库
    self.sel_mgr:dump_sel(path .. '/sensor.db')
end

function sensor_service:init_debug()
    debug.on_dump(function(...) self:on_dump(...) end)
end

function sensor_service:init_client_subscribe()
    client:SubscribeFrusFruAdded(function (ctx, FruId, FruName, FruPath)
        local ids = {id = FruId, name = FruName, path = FruPath}
        self.sdr_mgr:register_fru(utils.FRU_SDR_BY_ID, ids)
    end)

    client:ForeachFruObjects(function(obj)
        self.sdr_mgr:register_fru(utils.FRU_SDR_BY_OBJ, obj)
    end)

    client:SubscribeFrusFruRemoved(function (ctx, FruId, FruName, FruPath)
        self.sdr_mgr:unregister_fru(FruId, FruName, FruPath)
    end)

    -- 监听 HOST 的下电和复位状态，用于更新 BIOS Event
    self.sel_mgr:listen_host()
end

function sensor_service:on_import(ctx, datas, import_type)
    local data = json.decode(datas)
    if not data or not data.ConfigData or type(data.ConfigData) ~= 'table' then
        log:error('import data [%s] is invalid.', datas)
        return
    end

    -- 装备定制化
    if import_type == 'custom' then
        local customize_mgr = {
            ['BMCSet_QuerySELMaxNumValue'] = self.sel_mgr,
            ['BMCSet_SEL_Mode'] = self.sel_mgr,
            ['BMCSet_CustomDynamicSensorNumBase'] = self.sensor_mgr,
            ['BMCSet_PEF_Enable'] = self.pef_mgr
        }
        sensor_customize.on_import(ctx, data.ConfigData.CustomSettings, customize_mgr)
        return
    end

    -- 定义当前组件需要导入的配置名，以及对应处理的模块
    local import_map = {
        ['Sel'] = self.sel_mgr,
        ['SdrDev'] = self.sensor_mgr,
        ['Pef'] = self.pef_mgr
    }
    local ret
    for k, v in pairs(data.ConfigData) do
        repeat
            if not import_map[k] then
                log:notice('import %s has no object to process.', k)
                break
            end
            ret = import_map[k]:on_import(ctx, v)
            if ret ~= 0 then
                log:error('import %s has error occured (%s).', k, ret)
                error(custom_messages.CollectingConfigurationErrorDesc(k))
                break
            end
            log:info('import %s successfully.', k)
        until true
    end
end

function sensor_service:on_export(ctx, export_type)
    local data = {}

    -- 装备定制化
    if export_type == 'custom' then
        local customize_mgr = {
            ['BMCSet_QuerySELMaxNumValue'] = self.sel_mgr,
            ['BMCSet_SEL_Mode'] = self.sel_mgr,
            ['BMCSet_CustomDynamicSensorNumBase'] = self.sensor_mgr,
            ['BMCSet_PEF_Enable'] = self.pef_mgr
        }
        data.CustomSettings = sensor_customize.on_export(ctx, customize_mgr)
        return json.encode({ConfigData = data})
    end

    -- 定义当前组件需要导出的配置名，以及对应处理的模块
    local export_map = {
        ['Sel'] = self.sel_mgr,
        ['SdrDev'] = self.sensor_mgr,
        ['Pef'] = self.pef_mgr
    }
    for k, v in pairs(export_map) do
        repeat
            if not v then
                log:error('export %s has no object to process.', k)
                break
            end
            local ok, ret = pcall(function () return v:on_export(ctx) end)
            if not ok then
                log:error('export %s has error occured (%s).', k, ret)
                break
            end
            log:info('export %s successfully.', k)
            data[k] = ret
        until true
    end

    return json.encode({ConfigData = data})
end

function sensor_service:on_backup(ctx, filepath)
    if not utils_core.is_dir(filepath) then
        error(custom_messages.InvalidValue('******', 'filepath'))
    end
    local res = {
        {'sensor.db', '/data/opt/bmc/persistence.local/sensor.db'}
    }
    local name, src_path, ok
    for _, f in ipairs(res) do
        name = f[1]
        src_path = f[2]
        ok = mc_utils.copy_file(src_path, filepath .. '/' .. name)
        if not ok then
            log:mcf_error('[config_manage] Backup %s to destination path failed', name)
            log:operation(ctx:get_initiator(), 'sensor',
                'Set persistence manufacturer default configuration failed')
            error(base_messages.InternalError())
        end
    end

    log:operation(ctx:get_initiator(), 'sensor', 'Set manufacturer default configuration successfully')
    return res
end

function sensor_service:init_config()
    config.on_import(function(...) return self:on_import(...) end)
    config.on_export(function(...) return self:on_export(...) end)
    config.on_backup(function(...) return self:on_backup(...) end)
    config.on_recover(function(ctx)
        log:operation(ctx:get_initiator(), 'sensor', 'Recover manufacturer default configuration successfully')
     end)
end

-- 依赖检查
function sensor_service:check_dependencies()
    local admin = mc_admin.new()
    admin:parse_dependency(APP_WORKING_DIRECTORY .. '/mds/service.json')
    admin:check_dependency(self.bus)
end

function sensor_service:init()
    sensor_service.super.init(self)
    sensor_service:check_dependencies()

    -- 初始化持久化服务和资源对接
    require('mc.orm.object_manage').get_instance(self.db, self.bus):start()

    -- 初始化 服务句柄
    self:init_mgr()

    -- 初始化 RPC 接口
    self:init_rpc()

    -- 初始化 对象管理
    self:init_objmgr()

    -- 初始化 app 复位接口
    self:init_reboot()

    -- 初始化 app 一键日志收集接口
    self:init_debug()

    -- 初始化 app 导入导出接口
    self:init_config()

    skynet.fork(function()
        -- 初始化ipmi接口
        self:init_ipmi()
        -- 初始化订阅客户端接口
        self:init_client_subscribe()
    end)

    start_gc_task()
end

local sel_clz = {
    'BootError', 'SensorSelInfo'
}
 
local function includes(t, val)
    for _, v in ipairs(t) do
        if v == val then
            return true
        end
    end
    return false
end

function sensor_service:register(clz, obj)
    if clz:match("IpmiPef") then
        -- PEF 配置对象单独解析
        return self.pef_mgr:register(clz, obj)
    end
    if clz:match('IpmiSel') or includes(sel_clz, clz) then
        -- SEL 配置对象单独解析
        return self.sel_mgr:register(clz, obj)
    end
    if clz == 'Entity' then
        return self.entity_mgr:register(obj)
    end
    if clz == 'MCDLSDR' then
        return self.sdr_mgr:register(clz, obj)
    end
    if clz == 'DEASDR' then
        return self.sdr_mgr:register(clz, obj)
    end
    if clz == 'BMCEnables' then
        return self.global_mgr:register(obj)
    end
    if clz == 'DiscreteEvent' then
        self.sensor_mgr:register_event(obj)
    end
    if clz == 'ThresholdSensor' or clz == 'DiscreteSensor' then
        if self.sensor_mgr:register_sensor(clz, obj) then
            self.sdr_mgr:register(clz, obj)
        end
    end
end

function sensor_service:unregister(clz, obj)
    if clz == 'Entity' then
        return self.entity_mgr:unregister(obj)
    end
    if clz == 'DiscreteEvent' then
        self.sensor_mgr:unregister_event(obj)
    end
    if clz == 'DiscreteSensor' or clz == 'ThresholdSensor' then
        if self.sensor_mgr:unregister_sensor(obj) then
            self.sdr_mgr:unregister(obj)
        end
    end
end

return sensor_service
